/*
 * Copyright 2019 the go-netty project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package protocol

import (
	"encoding/binary"
	"io"
	"serialnet/model"
	"serialnet/netty"
	"serialnet/netty/codec"
	"serialnet/netty/utils"
	"sync"
)

// create a update msg codec
func NewMsgCodec(scheme uint32) codec.Codec {
	return &MsgCodec{scheme: scheme, buffers: sync.Pool{New: func() interface{} { return new([PackLength]byte) }}}
}

type MsgCodec struct {
	scheme  uint32 // 0 tcp 1 udp
	buffer  []byte
	buffers sync.Pool
}

const PackLength = 2048

func (*MsgCodec) CodecName() string {
	return "msg-codec"
}

func (v *MsgCodec) HandleRead(ctx netty.InboundContext, message netty.Message) {
	reader := utils.MustToReader(message)
	data := v.buffers.Get().(*[PackLength]byte) // make([]byte, v.maxFrameLength)
	rn, err := reader.Read(data[:])
	if rn <= 0 && err != nil {
		if err == io.EOF { // 对端已关闭
			ctx.Close(err)
		}
		v.buffer = nil
		return
	}
	if v.scheme != 0 {
		if rn < 7 { // 最小长度
			return
		}
		if data[0] != model.ClientStarter || data[rn-1] != model.Ender { // 校验帧头帧尾
			return
		}
		funcCode := data[1]
		msgLen := binary.BigEndian.Uint16(data[2:])
		if msgLen != uint16(rn-7) { // 检测正文长度
			return
		}
		crc := binary.BigEndian.Uint16(data[rn-3:])
		crc16 := Crc16(data[:rn-3])
		if crc16 != crc { // 校验CRC
			return
		}
		msg := &model.MsgFrame{Start: data[0], Func: model.MsgType(funcCode),
			Length: msgLen, Data: data[4 : rn-3], Crc: crc16, End: data[rn-1]}
		ctx.HandleRead(msg)

	} else {
		if v.buffer == nil {
			v.buffer = data[:rn]
		} else {
			v.buffer = append(v.buffer, data[:rn]...)
		}
		v.dataProcess(ctx)
	}
}

func (v *MsgCodec) dataProcess(ctx netty.InboundContext) {
	defer func() {
		if err := recover(); err != nil {
			v.buffer = nil
		}
	}()
	if v.buffer[0] != model.ClientStarter { // 校验帧头
		v.buffer = nil
		return
	}
	bufLen := len(v.buffer)
	var msgLen uint16
	if bufLen >= 7 {
		msgLen = binary.BigEndian.Uint16(v.buffer[2:])
		if msgLen > uint16(bufLen-7) { // 长度不足
			return
		}
		if model.Ender != v.buffer[msgLen+6] { // 结束符不正确
			v.buffer = nil
			return
		}
		funcCode := v.buffer[1]
		crc := binary.BigEndian.Uint16(v.buffer[msgLen+4:])
		crc16 := Crc16(v.buffer[:msgLen+4])
		if crc16 != crc { // 校验CRC
			v.buffer = nil
			return
		}
		msg := &model.MsgFrame{Start: v.buffer[0], Func: model.MsgType(funcCode),
			Length: msgLen, End: v.buffer[msgLen+6], Crc: crc16}
		if msgLen > 0 {
			msg.Data = v.buffer[4 : msgLen+4]
		}
		ctx.HandleRead(msg)
		if bufLen > int(msgLen+7) {
			v.buffer = v.buffer[msgLen+7:]
			v.dataProcess(ctx) // 循环处理
		} else {
			v.buffer = nil
		}
	}
}

func (v *MsgCodec) HandleWrite(ctx netty.OutboundContext, message netty.Message) {

	switch msg := message.(type) {
	case *model.MsgFrame:
		data := v.buffers.Get().(*[PackLength]byte)
		data[0] = model.ServerStarter
		data[1] = byte(msg.Func)
		msg.Length = uint16(len(msg.Data))
		binary.BigEndian.PutUint16(data[2:], msg.Length)
		copy(data[4:], msg.Data)
		crc := Crc16(data[:4+msg.Length])
		binary.BigEndian.PutUint16(data[4+msg.Length:], crc)
		data[msg.Length+6] = model.Ender
		ctx.HandleWrite(data[:msg.Length+7])
	default:
		ctx.HandleWrite(message)
	}
}

func invertUint16(srcBuf uint16) uint16 {
	var tmp uint16
	for i := 0; i < 16; i++ {
		if (srcBuf & (1 << i)) > 0 {
			tmp |= 1 << (15 - i)
		}
	}
	return tmp
}

func Crc16(data []byte) uint16 {
	wCRCin := uint16(0x0000)
	wCPoly := uint16(0x8408) //uint16(0x1021) // 0x8408
	//wCPoly = invertUint16(wCPoly)
	for i := 0; i < len(data); i++ {
		wCRCin ^= uint16(data[i])
		for j := 0; j < 8; j++ {
			if wCRCin&0x01 > 0 {
				wCRCin = (wCRCin >> 1) ^ wCPoly
			} else {
				wCRCin = wCRCin >> 1
			}
		}
	}
	return wCRCin
}
